package com.moonshineproject.plugin.actionscript.profiler {
	import com.moonshineproject.IDEModel;
	import com.moonshineproject.plugin.PluginBase;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.events.ServerSocketConnectEvent;
	import flash.events.TimerEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.net.ServerSocket;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	import flash.utils.setTimeout;
	import flash.utils.Timer;
	import no.doomsday.console.ConsoleUtil;
	public class SWFProfilerPlugin extends PluginBase {
		static public const PROFILER_HOST:String = "127.0.0.1";
		static public const PROFILER_PORT:int = 7120;
		
		override public function get name():String			{ return "SWF Profiler Plugin"; }
		override public function get author():String		{ return "Moonshine Project Team"; }
		override public function get description():String	{ return "Profiles SWF files."; }
		
		private static var configPath:String = "mm.cfg";
		
		private var profilerHelper:File;
		
		private var server:ServerSocket;
		private var client:Socket;
		
		private var remaining:uint = 0;
		private var bytes:ByteArray;
		private var profileItems:Vector.<ProfileItem>;
		private var cmd:int;
		private var raw:Array;
		
		private var updater:Timer = new Timer(1000);
		
		/** For each file, class and method */
		private var calls:Object;
		private var stackNum:Number;
		
		public function SWFProfilerPlugin() {
			ConsoleUtil.createCommand("p", start);
			ConsoleUtil.createCommand("s", stop);
			profilerHelper = File.applicationDirectory.resolvePath("profiler/Profiler.swf");
			updater.addEventListener(TimerEvent.TIMER, update);
		}
		
		override public function activate():void 
		{
			super.activate();
			
			registerCommand("startProfiler", start);
			
			//setTimeout(start, 1000);
		}
		
		override public function deactivate():void 
		{
			super.deactivate();
		}
		
		
		public function start(e:Event = null):void {
			if (!server) {
				server = new ServerSocket();
				server.bind(PROFILER_PORT);
				server.addEventListener(ServerSocketConnectEvent.CONNECT, clientConnected);
				server.listen();
			}
			
			ConsoleUtil.print("Profiler server "+server.listening+" on "+server.localAddress+" "+server.localPort);
			
			writePath();
			IDEModel.getInstance().mainView.stage.nativeWindow.addEventListener(Event.CLOSE, stop);
		}
		
		private function writePath():void {
			var file:File = File.userDirectory.resolvePath(configPath);
			if (!file.exists) {
				error("Config not found at "+file.nativePath);
				return;
			}
			var stream:FileStream = new FileStream();
			stream.open(file, FileMode.UPDATE);
			var config:String = stream.readUTFBytes(file.size);
			var r:RegExp = /^PreloadSwf=(.*)$/gm;
			var match:Array = config.match(r);
			var line:String = "PreloadSwf="+profilerHelper.nativePath+"?host="+PROFILER_HOST+"&port="+PROFILER_PORT;
			if (match.length > 0) {
				config = config.replace(r, line);
				stream.position = 0;
				stream.truncate();
				stream.writeUTFBytes(config);
			} else {
				stream.position = file.size;
				stream.writeUTFBytes("\n"+line);
			}
			stream.close();
		}
		
		public function stop(e:Event = null):void {
			IDEModel.getInstance().mainView.stage.nativeWindow.removeEventListener(Event.CLOSE, stop);
			var file:File = File.userDirectory.resolvePath(configPath);
			if (!file.exists) {
				error("Config not found at "+file.nativePath);
				return;
			}
			var stream:FileStream = new FileStream();
			stream.open(file, FileMode.UPDATE);
			var config:String = stream.readUTFBytes(file.size);
			var r:RegExp = /\r?\nPreloadSwf=(.*)/g;
			var match:Array = config.match(r);
			if (match.length > 0) {
				config = config.replace(r, "");
				stream.position = 0;
				stream.truncate();
				stream.writeUTFBytes(config);
			}
			stream.close();
		}
		
		
		private function clientConnected(e:ServerSocketConnectEvent):void {
			client = e.socket;
			client.addEventListener(ProgressEvent.SOCKET_DATA, clientData);
			client.addEventListener(Event.CLOSE, clientClose);
			server.removeEventListener(ServerSocketConnectEvent.CONNECT, clientConnected);
			
			print("Client connected");
			
			startProfiling();
			
			updater.start();
			
			//setTimeout(stopProfiling, 3000);
			//setTimeout(getProfiling, 3500);
			//setTimeout(getStacks, 3500);
			
		}
		private function update(e:TimerEvent):void {
			getStacks();
		}
		
		private function clientClose(e:Event):void {
			print("Client disconnected");
			server.addEventListener(ServerSocketConnectEvent.CONNECT, clientConnected);
			client = null;
			updater.stop();
		}
		
		private function startProfiling():void {
			print("Starting profiling");
			sendByte(1);
		}
		private function stopProfiling():void {
			print("Stopping profiling");
			sendByte(2);
		}
		private function getProfiling():void {
			print("Retrieving profiling data...");
			profileItems = new Vector.<ProfileItem>();
			sendByte(3);
		}
		private function getStacks():void {
			print("Retrieving stack data...");
			stackNum = 0;
			calls = { };
			sendByte(5);
		}
		private function sendByte(byte:int):void {
			if (client && client.connected) {
				client.writeByte(byte);
				client.flush();
			}
		}
		
		
		private function clientData(e:ProgressEvent = null):void {
			if (remaining > 0) {
				var len:int = Math.min(remaining, client.bytesAvailable);
				client.readBytes(bytes, bytes.length, len);
				remaining -= len;
				
				if (remaining <= 0) {
					switch (cmd) {
						case 1:	parseProfiling(); break;
						//case 2:	parseRaw(); break;
						case 3: parseStacks(); break;
					}
				}
			} else {
				cmd = client.readByte();
				remaining = client.readUnsignedInt();
				bytes = new ByteArray();
			}
			if (client.bytesAvailable > 0) {
				clientData();
			}
			
		
		}
		
		
		private function parseProfiling():void {
			//ConsoleUtil.print("PARSE "+profiledBytes.length);
			bytes.position = 0;
			while (bytes.position < bytes.length) {
				var self:Number = bytes.readDouble();
				var cumu:Number = bytes.readDouble();
				var sum:Number = bytes.readDouble();
				//var num:Number = profiledBytes.readDouble();
				var len:uint = bytes.readUnsignedInt();
				//ConsoleUtil.print("PARSE S "+sum+" "+len);
				var func:String = bytes.readUTFBytes(len);
				profileItems.push(new ProfileItem(func, self, cumu, sum));
			}
			print("Profiling data parsed.");
			profileItems.sort(sortBySelf);
			for (var i:int = 0; i < profileItems.length; i++) {
				var p:ProfileItem = profileItems[i];
				//print(p.sum+"\t"+p.num+"\t"+p.name);
				print(p.self+"\t"+p.cumu+"\t"+p.name);
			}
		}
		
		private function parseStacks():void {
			print("Parsing stacks...");
			var stacks:Vector.<Object> = bytes.readObject();
			
			var funcs:Object;
			var funcName:String;
			var file:String;
			
			for each (var stack:Array in stacks) {
				for each (var frame:Object in stack) {
					file = frame.file;
					if (file) {
						funcName = frame.name;
						funcs = calls[file];
						//new File(
						if (!funcs) {
							calls[file] = funcs = { };
						}
						
						if (!(funcName in funcs)) funcs[funcName] = 0;
						funcs[funcName]++;
					}
				}
				stackNum++;
			}
			for (file in calls) {
				funcs = calls[file];
				for (funcName in funcs) {
					//print("F "+file+" "+funcName+" "+funcs[funcName]);
					var num:Number = funcs[funcName];
					print("F "+funcName+" "+Math.round(num/stackNum*10000)/100+"%");
				}
			}
		}
		
		private function sortBySelf(a:ProfileItem, b:ProfileItem):int {
			if (a.self < b.self) {
				return 1;
			} else if (a.self > b.self) {
				return -1;
			}
			return 0;
		}
	}
}